package com.motu.monstercity.server.game.verticle;


import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message.Builder;
import com.googlecode.protobuf.format.JsonFormat;
import com.motu.monstercity.module.common.network.ErrorExtend;
import com.motu.monstercity.module.common.network.RequestParam;
import com.motu.monstercity.module.common.network.ResponseUtils;
import com.motu.monstercity.module.common.protobuf.ProtobufManager;
import com.motu.monstercity.module.common.redis.RedisKey;
import com.motu.monstercity.module.common.utility.ServerParam;
import com.motu.monstercity.protocol.CmdEnum;
import com.motu.monstercity.protocol.CmdEnum.EnumCmdID;
import com.motu.monstercity.module.common.utility.EventBusConstant;
import com.motu.monstercity.server.game.logic.GameUser;
import com.motu.monstercity.server.game.userdata.UserExtInfo;
import com.motu.monstercity.server.game.userdata.UserInfo;
import com.motu.vertx.module.utility.base.ServerConfig;
import com.motu.vertx.module.utility.exception.LockException;
import com.motu.vertx.module.utility.monitor.MonitorManager;
import com.motu.vertx.module.utility.mysql.MysqlClient;
import com.motu.vertx.module.utility.mysql.MysqlTool;
import com.motu.vertx.module.utility.redis.RedisTools;
import com.motu.vertx.module.utility.threadpool.DBLogManager;
import com.motu.vertx.module.utility.toolset.ErrorWord;
import com.motu.vertx.module.utility.toolset.TimeUtils;
import com.motu.vertx.module.utility.toolset.Tool;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.Message;
import io.vertx.core.eventbus.MessageConsumer;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class GameVerticle extends AbstractVerticle {
	public static final Logger logger = LoggerFactory.getLogger(GameVerticle.class);
	//public WebClient webClient;
	public static GameVerticle instance;

	@Override
	public void start(Promise<Void> future) {
		instance = this;
		//webClient = WebClient.create(vertx, new WebClientOptions().setKeepAlive(true));
		EventBus eb = vertx.eventBus();
		String address = EventBusConstant.getAddress(EventBusConstant.GAME_LOGIC);
		// 测试服如果有部署备份游戏进程，则另外命名
		if (ServerConfig.isDebugServer() && ServerConfig.isBackups()) {
			address += EventBusConstant.BACKUPS;
		}

		MessageConsumer<JsonObject> consumer = eb.consumer(address);
		logger.info("####### start GameVerticle address:" + address);
		consumer.handler(message -> {

			// 处理请求消息
			handleMessage(message);

		});

		String ebName2 = address;
		consumer.completionHandler(res2 -> {
			if (res2.succeeded()) {
				logger.info("GameVerticle handler registration has reached all nodes ebName:" + ebName2);
				future.complete();
			} else {
				logger.error("GameVerticle Registration failed! " + res2.cause().toString());
				future.fail("GameVerticle handler registration failed! " + res2.cause().toString());
			}
		});

	}

	/**
	 * 处理请求消息
	 */
	public void handleMessage(Message<JsonObject> message) {
		if (message.body().isEmpty()) {
			message.reply(new JsonObject());
			return;
		}
		int cmdCode = message.body().getInteger("code");
		long userId = message.body().getLong("uid");
		int language = message.body().getInteger("lan");
		byte[] reqData = message.body().getBinary("data");
		int messageId = message.body().getInteger("mid");

		EnumCmdID eid = CmdEnum.EnumCmdID.forNumber(cmdCode);
		if (eid == null) {//未定义的协议
			JsonObject obj = ResponseUtils.getResponseError(userId, cmdCode, EnumCmdID.CMD_DEFAULT.name(), ErrorWord.PROTOCOL_UNDEFINE, reqData, language, messageId);
			message.reply(obj);
			return;
		}
		String cmdName = eid.name();
		//        logger.info("GameManager handleMessage cmdName:"+cmdName+" userId:"+userId);
		String packetPath = "com.motu.monstercity.server.game.logichandler.";
		String strMethod = cmdName;
		String strClass = ProtobufManager.getHandlerNameByCmd(cmdName);
		Class<?> threadClazz;
		long startTime = System.currentTimeMillis();

		// 判断是否限流
		boolean[] isUpdateRequestNum = new boolean[1];// 是否有更新协议请求数量
		if (checkProtocolRequestLimit(cmdCode, isUpdateRequestNum)) {
			JsonObject obj = ResponseUtils.getResponseError(userId, cmdCode, cmdName, ErrorWord.PROTOCOL_LIMIT, reqData, language, messageId);
			message.reply(obj);
			return;
		}

		try {
			threadClazz = Class.forName(packetPath + strClass);
			Method method;
			method = threadClazz.getMethod(cmdName, RequestParam.class);
			JsonObject obj;
			RequestParam requestParam = new RequestParam(userId, (byte)language, reqData);
			Object result = method.invoke(null, requestParam);
			if (result == null) {
				message.reply(new JsonObject());
				return;
			}
			if (result instanceof Builder) {
				Builder builder = (Builder) result;
				obj = ResponseUtils.getResponseData(cmdCode, builder.build().toByteArray());
			} else if (result instanceof ErrorWord) {
				ErrorWord errorWord = (ErrorWord) result;
				obj = ResponseUtils.getResponseError(userId, cmdCode, cmdName, errorWord, reqData, language, messageId);
			} else if (result instanceof ErrorExtend) {
				ErrorExtend errorExtend = (ErrorExtend) result;
				obj = ResponseUtils.getResponseError(userId, cmdCode, cmdName, errorExtend.getErrorWord(), reqData, language, messageId, errorExtend.getType(), errorExtend.getParam());
			}else {
				// 不存在的响应类型
				obj = ResponseUtils.getResponseError(userId, cmdCode, cmdName, ErrorWord.PROTOCOL_UNDEFINE, reqData, language, messageId);
			}
			message.reply(obj);
		} catch (InvocationTargetException e) {// 反射异常
			Throwable t = e.getTargetException();// 获取反射目标异常
			ErrorWord errorWord;
			if (t instanceof InvalidProtocolBufferException) {
				errorWord = ErrorWord.PROTOCOL_PARSE_FAILED;// 协议解析失败
			} else if (t instanceof LockException) {
				errorWord = ErrorWord.LOCK_TIMEOUT;// 获取锁超时(反射调用方法的加锁异常)
			} else {
				errorWord = ErrorWord.LOGIC_EXCEPTION;// 逻辑处理错误
			}
			JsonObject obj = ResponseUtils.getResponseError(userId, cmdCode, cmdName, errorWord, reqData, language, messageId);
			message.reply(obj);
			String errMsg = Tool.getException(t);
			logger.error("### logic error " + errMsg);
			DBLogManager.addServerCrashLog(userId, errMsg);
		} catch (Exception e) {
			if (ServerConfig.isDebugServer()) {
				// 测试服临时调换，下发错误和记录异常的顺序
				// 测试能否正常获取BaseHandler.getResponseError中找不到CSGameSystemLogicErrorResponse的异常
				String errMsg = Tool.getException(e);
				logger.error("### logic error " + errMsg);
				DBLogManager.addServerCrashLog(userId, errMsg);
				ErrorWord errorWord = ErrorWord.LOGIC_EXCEPTION;// 逻辑处理错误
				JsonObject obj = ResponseUtils.getResponseError(userId, cmdCode, cmdName, errorWord, reqData, language, messageId);
				message.reply(obj);
			} else {
				ErrorWord errorWord = ErrorWord.LOGIC_EXCEPTION;// 逻辑处理错误
				JsonObject obj = ResponseUtils.getResponseError(userId, cmdCode, cmdName, errorWord, reqData, language, messageId);
				message.reply(obj);
				String errMsg = Tool.getException(e);
				logger.error("### logic error " + errMsg);
				DBLogManager.addServerCrashLog(userId, errMsg);
			}
		} finally {
			// 如果有更新请求协议数量，则执行完需要扣除
			if (isUpdateRequestNum[0]) {
				RedisTools.addHashField(RedisKey.getProtocolRequestKey(), String.valueOf(cmdCode), -1);
			}

			//记录超时命令
			long endTime = System.currentTimeMillis();
			long runTime = endTime - startTime;
			if (runTime > ServerParam.LOGIC_WARNING_RUN_TIME) {
				String requestJson;
				try {
					if (reqData.length > 5000) {
						requestJson = "params too long";
					} else {
						Builder requestBuild = ProtobufManager.getRequestBuild(cmdName, 0);
						requestBuild.mergeFrom(reqData);
						requestJson = JsonFormat.printToString(requestBuild.build());
						if (requestJson.length() > 5000) {
							requestJson = "params too long";
						}
					}
				} catch (Exception e) {
					requestJson = "build error";
				}
				String sql = "INSERT INTO timeout_cmd(cmd,time,user_id,add_time,request) VALUES(?,?,?,?,?);";
				JsonArray params = new JsonArray();
				params.add(cmdName);
				params.add(runTime);
				params.add(userId);
				params.add(TimeUtils.getCurTime());
				params.add(requestJson);
				MysqlTool.updateToDb(MysqlClient.getServerPool(), sql, params, handler -> {
					if (handler.failed()) {
						logger.error(handler.cause().getMessage());
					}
				});
			}
		}
	}

	/**
	 * 判断是否限流
	 *
	 * @param cmdCode            协议码
	 * @param isUpdateRequestNum 是否有更新协议请求数量
	 * @return true限流 false不限流
	 */
	public boolean checkProtocolRequestLimit(int cmdCode, boolean[] isUpdateRequestNum) {
		if (ServerParam.PROTOCOL_REQUEST_COUNT_TYPE > 0) {
			Integer config = MonitorManager.getProtocolRequestNum(cmdCode);
			int type = 0;// 0无需处理 1只记录 2判断限流

			// 全部记录
			if (ServerParam.PROTOCOL_REQUEST_COUNT_TYPE == 1) {
				// 没有配置限流，则只需要记录
				if (config == null || config.intValue() == 0) {
					type = 1;
				} else {
					type = 2;
				}
			}
			// 配表记录
			else if (ServerParam.PROTOCOL_REQUEST_COUNT_TYPE == 2 && config != null) {
				// 没有限流，则只需要记录
				if (config.intValue() == 0) {
					type = 1;
				} else {
					type = 2;
				}
			}

			String key = RedisKey.getProtocolRequestKey();
			if (type == 1) {
				isUpdateRequestNum[0] = true;
				RedisTools.addHashField(key, String.valueOf(cmdCode), 1);
			} else if (type == 2) {
				long requestNum = Future.await(RedisTools.addHashField(key, String.valueOf(cmdCode), 1));
				if (requestNum >= config.intValue()) {
					logger.warn("protocol request limit cmdCode = {} requestNum = {} configNum = {}", cmdCode, requestNum, config.intValue());
					RedisTools.addHashField(key, String.valueOf(cmdCode), -1);
					return true;
				} else {
					isUpdateRequestNum[0] = true;
				}
			}
		}
		return false;
	}

}
